import { observe } from './observe/index.js'
import Watcher from './observe/watcher.js'

function stateMixin(Vue) {
  Vue.prototype.$watch = function (exprOrFn, cb, options) {
    debugger
    const vm = this
    //  user: true 这里表示是一个用户watcher
    let watcher = new Watcher(vm, exprOrFn, cb, {
      ...options,
      user: true
    })

    // 如果有immediate属性 代表需要立即执行回调
    if (options.immediate) {
      cb() //如果立刻执行
    }
  }
}

/**
 * 初始化状态: props > methods > data > computed > watch
 */
function initState(vm) {
  // 获取传入的数据对象
  const opts = vm.$options
  if (opts.props) {
    initProps(vm)
  }
  if (opts.methods) {
    initMethod(vm)
  }
  if (opts.data) {
    initData(vm)
  }
  if (opts.computed) {
    initComputed(vm)
  }
  if (opts.watch) {
    initWatch(vm)
  }
}

function initProps(vm) {}
function initMethod(vm) {}

/**
 * 初始化 data 数据
 */
function initData(vm) {
  debugger
  let data = vm.$options.data

  // data 支持 function + object 形式
  data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}

  // 把data数据代理到vm 也就是Vue实例上面 我们可以使用this.a来访问this._data.a
  // for in 这里表示原型链上的 key-val 也代理, 见 forin_objectkeys.md
  for (const key in data) {
    proxy(vm, '_data', key)
  }

  // 对数据进行观测 => 响应式核心逻辑
  observe(data)
}

function initComputed(vm) {}

/**
 * 初始化 watch
 */
function initWatch(vm) {
  let watch = vm.$options.watch
  for (const k in watch) {
    const handler = watch[k] //用户自定义watch的写法可能是数组 对象 函数 字符串
    if (Array.isArray(handler)) {
      // 如果是数组就遍历进行创建
      handler.forEach((handle) => createWatcher(vm, k, handle))
    } else {
      createWatcher(vm, k, handler)
    }
  }
}

// 创建watcher的核心
function createWatcher(vm, exprOrFn, handler, options = {}) {
  if (typeof handler === 'object') {
    options = handler //保存用户传入的对象
    handler = handler.handler //这个代表真正用户传入的函数
  }

  if (typeof handler === 'string') {
    handler = vm[handler] // 代表传入的是定义好的methods方法
  }

  // 调用vm.$watch创建用户watcher
  return vm.$watch(exprOrFn, handler, options)
}

// 数据代理
// this[sourceKey][key] => this[key]
function proxy(object, sourceKey, key) {
  Object.defineProperty(object, key, {
    get() {
      return object[sourceKey][key]
    },
    set(newValue) {
      object[sourceKey][key] = newValue
    }
  })
}

export { initState, stateMixin }
